/*
 * HttpHost.cpp
 *
 */

#include "../../common/LogFile.h"
#include "../../common/ServerConfig.h"
#include "../../common/StrUtils.h"
#include "HttpAuthentication.h"
#include "HttpHost.h"
#include "HttpResponse.h"
#include "HttpFileBody.h"
#include "HttpStringBody.h"
#include "HttpGet.h"
#include "HttpPost.h"
#include "../Process.h"
#include "UrlEncode.h"
#include "TagManager.h"
#include "HttpParser.h"
#include "HttpCodes.h"

const std::string HttpHost::PROTOCOL_NAME = "HTTP";
const std::string HttpHost::MAYOR_VERSION = "1";
const std::string HttpHost::MINOR_VERSION = "0";
const std::string HttpHost::HTTP_VERSION = HttpHost::PROTOCOL_NAME + "/"
		+ HttpHost::MAYOR_VERSION + "." + HttpHost::MINOR_VERSION;

HostException::HostException(const std::string& msg) throw () :
	msg(msg) {
}

HttpHost::HttpHost(const ServerConfig& config, LogFile& logFile,
		const std::string& id) :
	config(config),
	logFile(logFile),
	id(id) {
}

HttpHost::~HttpHost() {}

void HttpHost::generateResponse(HttpResponse& response,
		const HttpRequest& request) {
	if (authenticated(response, request)) {
		std::string resource, params;
		UrlEncode::splitUri(request.getUri(), resource, params);
		if (!staticProcess(response, request, resource, params))
			if (!dynamicProcess(response, request, resource, params)) {
				errorProcess(response, HttpCodes::NOT_FOUND);
				logFile.addAccessInfo(id, request.getRequestLine(),
						response.getCode());
				logFile.addClientError(id, resourceExtension(resource) +
						":Tipo de archivo no registrado");
			}
	}
}

bool HttpHost::staticProcess(HttpResponse& response, const HttpRequest& request,
		const std::string& resource, const std::string& params) {
	bool responseGenerated = false;
	try {
		std::string type = config.getStaticType(resourceExtension(resource));
		HttpBody* body = new HttpFileBody(resourcePath(resource));
		response.setCode(HttpCodes::OK);
		response.addHeader(new HttpHeader(HttpHeader::CONTENT_TYPE, type));
		response.setBody(body);
		responseGenerated = true;
		logFile.addAccessInfo(id, request.getRequestLine(), response.getCode());
	}
	catch (const MessageException& e) {
		errorProcess(response, HttpCodes::NOT_FOUND); // no encontrado
		responseGenerated = true;
		logFile.addAccessInfo(id, request.getRequestLine(), response.getCode());
		logFile.addFileNotFound(id ,resourcePath(resource));
	}
	catch (const std::string& s) {
		// El tipo MIME no esta registrado como estático
	}
	return responseGenerated;
}

bool HttpHost::dynamicProcess(HttpResponse& response, const HttpRequest& request,
		const std::string& resource, const std::string& params) {
	bool responseGenerated = false;
	std::string ext = resourceExtension(resource);
	std::string filename = resourcePath(resource);
	try {
		std::string command = config.getDynamicType(ext);
		TagManager::replace("{NAME}", filename, command);
		process.setCommand(command);
		process.addEnvVar("REDIRECT_STATUS", "1");
		process.addEnvVar("DOCUMENT_ROOT", config.getRootPath());
		process.addEnvVar("SERVER_PROTOCOL", "HTTP/1.1");
		process.addEnvVar("SCRIPT_FILENAME", filename);
		process.addEnvVar("GATEWAY_INTERFACE", "CGI/1.1");
		process.addEnvVar("REQUEST_URI", resource);
		process.addEnvVar("SCRIPT_NAME", resource);
		process.addEnvVar("REQUEST_METHOD", request.getMethod());

		std::vector<char> buffer(DATA_BUFFER_SIZE);
		size_t read;
		if (request.getMethod() == HttpRequest::GET) {
			process.addEnvVar("QUERY_STRING", params);
			process.exe();
		}
		else if (request.getMethod() == HttpRequest::POST) {
			process.addEnvVar("CONTENT_TYPE",
					request.getHeader(HttpHeader::CONTENT_TYPE));
			process.addEnvVar("CONTENT_LENGTH",
					request.getHeader(HttpHeader::CONTENT_LENGTH));

			process.exe();

			size_t size = request.getBody().getSize();
			std::vector<char> buffer(size);
			request.getBody().getStream().read(buffer.data(), buffer.size());
			read = request.getBody().getStream().gcount();
			while (read > 0) {
				process.getCin().write(buffer.data(), read);
				request.getBody().getStream().read(buffer.data(), buffer.size());
				read = request.getBody().getStream().gcount();
			}
		}
		std::stringstream data;
		read = process.getCout().read(buffer.data(), buffer.size());
		while (read > 0) {
			data.write(buffer.data(), read);
			read = process.getCout().read(buffer.data(), buffer.size());
		}
		process.wait(); // espero a que termine

		// Verifico que la salida del script sea valida y la cargo al response
		cgiOutputProcess(response, request, data, resource);
		responseGenerated = true;
	}
	catch (const MessageException& e) {
		// Al request le faltan encabezados
		errorProcess(response, HttpCodes::BAD_REQUEST);
		responseGenerated = true;
		logFile.addAccessInfo(id, request.getRequestLine(), response.getCode());
		logFile.addFileNotFound(id, filename);
	}
	catch (const std::string& s) {
		// El tipo MIME no esta registrado como dinámico
	}
	return responseGenerated;
}

void HttpHost::cgiOutputProcess(HttpResponse& response, const HttpRequest& request,
		std::stringstream& data, const std::string& scriptName) {
	try {
		HttpParser::parseCgiHeaders(response, data);
		logFile.addAccessInfo(id, request.getRequestLine(), response.getCode());
	}
	catch (const ParserException& e) {
		// Los encabezados de respuesta del script estan mal formados o faltan
		errorProcess(response, e.errno());
		logFile.addAccessInfo(id, request.getRequestLine(), response.getCode());
		logFile.addSystemError(scriptName + " " + e.what());
	}
}

void HttpHost::errorProcess(HttpResponse& response, int code) {
	response.setCode(code);
	std::string ext;
	// Cargo la página registrada para el código o devuelvo solo el Status Line
	try {
		std::string resource = config.getErrorHandler(
				StrUtils::toString(response.getCode()));
		ext = resourceExtension(resource);
		std::string type = config.getStaticType(ext);
		HttpBody* body = new HttpFileBody(resourcePath(resource));
		response.addHeader(new HttpHeader(HttpHeader::CONTENT_TYPE, type));
		response.setBody(body);
	}
	catch (const std::string& e) {
		if(ext.empty())
			// No hay un manejador registrado para este código de error
			logFile.addSystemError(e + ": " + StrUtils::toString<int>(code));
		else
			// El tipo de página para este código de error no está regisrtado
			logFile.addSystemError("El tipo estático " + ext +
					" del código de error " + StrUtils::toString<int>(code)
					+ " no está registrado");
	}
	catch (const MessageException& e) {
		// La página de error configurada para este código no existe
		logFile.addSystemError("La página de error configurada para el código "
				+ StrUtils::toString<int>(code) + " no existe.");
	}
}

bool HttpHost::authenticated(HttpResponse& response,
		const HttpRequest& request) {
	bool authenticated = true;
	if (config.getProtectionFlag()) {
		HttpAuthentication client(request, config);
		if (!client.isAuthenticated()) {
			errorProcess(response, HttpCodes::UNAUTHORIZED);
			HttpHeader* h = new HttpHeader("WWW-Authenticate",
					"Basic realm=\"" + config.getServerId() + "\"");
			response.addHeader(h);
			authenticated = false;
			logFile.addAccessInfo(id, request.getRequestLine(),
					response.getCode());
		}
	}
	return authenticated;
}

void HttpHost::terminate() {
	process.kill();
}

std::string HttpHost::resourcePath(const std::string& resource) const {
	unsigned int firstBar = resource.find_first_of('/');
	return config.getRootPath() + resource.substr(firstBar);
}

std::string HttpHost::resourceExtension(const std::string& resource) const {
	return resource.substr(resource.find_last_of('.') + 1);
}
